﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.WebPages;
using System.Web.WebPages.Scope;
using System.Xml;
using System.Xml.Serialization;
using EventbriteApi.Model;

public static class Eventbrite
{
    private const string UserInfoUrl = "https://www.eventbrite.com/xml/user_get";
    private const string UserListEventsUrl = "https://www.eventbrite.com/xml/user_list_events";
    private const string UserCreateTicketUrl = "https://www.eventbrite.com/xml/ticket_new";
    private const string UserCreateEventUrl = "https://www.eventbrite.com/xml/event_new";
    private const string GetEventUrl = "https://www.eventbrite.com/xml/event_get";

    private const string InitializedExceptionMessage = "The Eventbrite Helper has not been initialized. You should call the Initialize method.";
    private const string ArgumentNullExceptionMessage = "Argument cannot be null or empty.";

    private static readonly object _app = new object();
    private static readonly object _user = new object();
    private static readonly object _userPassword = new object();
    private static readonly object _userKey = new object();
    private static readonly object _initializedKey = new object();

    public static string AppKey
    {
        get
        {
            if (!Initialized)
            {
                throw new InvalidOperationException(InitializedExceptionMessage);
            }

            return (string)(ScopeStorage.CurrentScope[_app] ?? "");
        }

        private set
        {
            if (value == null)
            {
                throw new ArgumentNullException("AppKey");
            }

            ScopeStorage.CurrentScope[_app] = value;
        }
    }

    public static string User
    {
        get
        {
            if (!Initialized)
            {
                throw new InvalidOperationException(InitializedExceptionMessage);
            }

            return (string)(ScopeStorage.CurrentScope[_user] ?? "");
        }

        private set
        {
            if (value == null)
            {
                throw new ArgumentNullException("User");
            }

            ScopeStorage.CurrentScope[_user] = value;
        }
    }

    public static string Password
    {
        get
        {
            if (!Initialized)
            {
                throw new InvalidOperationException(InitializedExceptionMessage);
            }

            return (string)(ScopeStorage.CurrentScope[_userPassword] ?? "");
        }

        private set
        {
            if (value == null)
            {
                throw new ArgumentNullException("Password");
            }

            ScopeStorage.CurrentScope[_userPassword] = value;
        }
    }

    public static string UserKey
    {
        get
        {
            if (!Initialized)
            {
                throw new InvalidOperationException(InitializedExceptionMessage);
            }

            return (string)(ScopeStorage.CurrentScope[_userKey] ?? "");
        }

        private set
        {
            if (value == null)
            {
                throw new ArgumentNullException("UserKey");
            }

            ScopeStorage.CurrentScope[_userKey] = value;
        }
    }

    private static bool Initialized
    {
        get
        {
            return (bool)(ScopeStorage.CurrentScope[_initializedKey] ?? false);
        }

        set
        {
            ScopeStorage.CurrentScope[_initializedKey] = value;
        }
    }

    /// <summary>
    /// Initializes the Eventbrite helper.
    /// </summary>
    /// <param name="appKey">Your Eventbrite application key.  The application key is a string assigned by Eventbrite which identifies the application making the method call.</param>
    /// <param name="userKey">The user key is a string assigned by Eventbrite which identifies the user making the method call. A valid user key is the recommended method for user authentication with the Eventbrite API, instead of User/UserPassword.</param>
    public static void Initialize(string appKey, string userKey = "")
    {
        if (appKey.IsEmpty())
        {
            throw new ArgumentException(ArgumentNullExceptionMessage, "appKey");
        }

        AppKey = appKey;
        UserKey = userKey;

        Initialized = true;
    }

    /// <summary>
    /// Initializes the Eventbrite helper.
    /// </summary>
    /// <param name="appKey">Your Eventbrite application Key.  The application key is a string assigned by Eventbrite which identifies the application making the method call.</param>
    /// <param name="user">Your Eventbrite user name.</param>
    /// <param name="password">Your Eventbrite password.</param>
    public static void Initialize(string appKey, string user, string password)
    {
        if (appKey.IsEmpty())
        {
            throw new ArgumentException(ArgumentNullExceptionMessage, "appKey");
        }

        if (user.IsEmpty())
        {
            throw new ArgumentException(ArgumentNullExceptionMessage, "user");
        }

        if (password.IsEmpty())
        {
            throw new ArgumentException(ArgumentNullExceptionMessage, "password");
        }

        AppKey = appKey;
        User = user;
        Password = password;

        Initialized = true;
    }

    /// <summary>
    /// This method returns a list of user's events.
    /// </summary>
    /// <param name="user">The user email. Defaults to the authenticated user if not provided.</param>
    /// <param name="doNotDisplay">Comma separated list without spaces. Valid options include: description, venue, logo, style, organizer, tickets.</param>
    /// <param name="eventStatuses">Comma separated list without spaces. Valid options include: live, started, ended.</param>
    /// <param name="ascOrDesc">Sort the results based on event start_date. Valid options include: "asc" for results in ascending order or "desc" for descending order. Defaults to "asc".</param>
    public static IList<Event> GetUserEventList(
                                    string user = "",
                                    string doNotDisplay = "",
                                    string eventStatuses = "",
                                    string ascOrDesc = "asc")
    {
        NameValueCollection queryStringValues = new NameValueCollection();
        queryStringValues.Add("do_not_display", doNotDisplay);
        queryStringValues.Add("event_statuses", eventStatuses);
        queryStringValues.Add("asc_or_desc", ascOrDesc);

        var eventlistUrl = BuildRequestUrl(UserListEventsUrl, user, queryStringValues);

        var receiveStream = new WebClient().OpenRead(eventlistUrl);
        var response = new StreamReader(receiveStream).ReadToEnd();

        XmlSerializer serializer = new XmlSerializer(typeof(EventList));
        if (serializer.CanDeserialize(new XmlTextReader(new StringReader(response))))
        {
            var eventList = serializer.Deserialize(new StringReader(response)) as EventList;
            return eventList.Events;
        }
        else
        {
            DeserializeError(response);
        }

        return null;
    }

    /// <summary>
    /// This method creates new fixed-price or donation ticket types. It returns the ID of the newly created ticket.
    /// </summary>
    /// <param name="eventId">The event ID. The event must have been previously created using /event_new.</param>
    /// <param name="name">The ticket name.</param>
    /// <param name="price">The ticket price. Enter 0.00 for free tickets. Leave blank for a donation.</param>
    /// <param name="quantity">The number of tickets available. Not required for donations.</param>
    /// <param name="isDonation">0 for fixed-price tickets, 1 for donations. 0 will be used by default if not provided.</param>
    /// <param name="description">The ticket description.</param>
    /// <param name="startSales">The date and time when ticket sales start, in ISO 8601 format (e.g., “2007-12-31 23:59:59″).</param>
    /// <param name="endSales">The date and time when ticket sales stop, in ISO 8601 format (e.g., “2007-12-31 23:59:59″).</param>
    /// <param name="includeFee">0 to add the Eventbrite service fee on top of ticket price, or 1 to include it in the ticket price. 0 will be used by default if not provided.</param>
    /// <param name="min">The minimum number of tickets per order.</param>
    /// <param name="max">The maximum number of tickets per order.</param>
    public static Process CreateNewTicket(
                              int eventId,
                              string name,
                              float price,
                              int quantity,
                              bool isDonation = false,
                              string description = "",
                              string startSales = "",
                              string endSales = "",
                              int includeFee = 0,
                              int min = 0,
                              int max = 0)
    {
        NameValueCollection queryStringValues = new NameValueCollection();
        queryStringValues.Add("event_id", eventId.ToString());
        queryStringValues.Add("name", name);
        queryStringValues.Add("price", price.ToString());
        queryStringValues.Add("quantity", quantity.ToString());
        queryStringValues.Add("is_donation", isDonation ? "0" : "1");
        queryStringValues.Add("description", description);
        queryStringValues.Add("start_sales", startSales);
        queryStringValues.Add("end_sales", endSales);
        queryStringValues.Add("include_fee", includeFee.ToString());
        queryStringValues.Add("min", min == 0 ? "" : min.ToString());
        queryStringValues.Add("max", max == 0 ? "" : max.ToString());

        var createTicketUrl = BuildRequestUrl(UserCreateTicketUrl, "", queryStringValues);
        var receiveStream = new WebClient().OpenRead(createTicketUrl);
        var response = new StreamReader(receiveStream).ReadToEnd();

        XmlSerializer serializer = new XmlSerializer(typeof(Process));
        if (serializer.CanDeserialize(new XmlTextReader(new StringReader(response))))
        {
            return serializer.Deserialize(new StringReader(response)) as Process;
        }
        else
        {
            DeserializeError(response);
        }

        return null;
    }

    /// <summary>
    /// This method creates a new event. It returns the ID of the newly created event.
    /// </summary>
    /// <param name="title">The event title.</param>
    /// <param name="startDate">The event start date and time, in ISO 8601 format (e.g., “2007-12-31 23:59:59″).</param>
    /// <param name="endDate">The event end date and time, in ISO 8601 format (e.g., “2007-12-31 23:59:59″).</param>
    /// <param name="timezone">The event time zone in relation to GMT (e.g., “GMT+01″, “GMT+02″, “GMT-01″).</param>
    /// <param name="description">The event description.</param>
    /// <param name="privacy">0 for a private event, 1 for a public event. If not provided, will default to 1.</param>
    /// <param name="personalizedUrl">The event registration URL. If you pass “testevent”, the event will be accessible at “http://testevent.eventbrite.com”.</param>
    /// <param name="venueId">The event venue ID. The venue must have been previously created using /venue_new.</param>
    /// <param name="organizerId">The event organizer ID. The organizer must have been previously created using /organizer_new.</param>
    /// <param name="capacity">The maximum number of people who can attend the event.</param>
    /// <param name="currency">The event currency in ISO 4217 format (e.g., “USD”, “EUR”).</param>
    /// <param name="status">The event status. Allowed values are “draft”, “live” for new events. If not provided, status will be “draft”, meaning that the event registration page will not be available publicly.</param>
    /// <param name="customHeader">Custom HTML header for your registration page.</param>
    /// <param name="customFooter">Custom HTML footer for your registration page.</param>
    /// <param name="backgroundColor">Custom hexadecimal color for your registration page. Format: FFFFFF without the pound.</param>
    /// <param name="textColor">Custom hexadecimal color for your registration page. Format: FFFFFF without the pound.</param>
    /// <param name="linkColor">Custom hexadecimal color for your registration page. Format: FFFFFF without the pound.</param>
    /// <param name="titleTextColor">Custom hexadecimal color for your registration page. Format: FFFFFF without the pound.</param>
    /// <param name="boxBackgroundColor">Custom hexadecimal color for your registration page. Format: FFFFFF without the pound.</param>
    /// <param name="boxTextColor">Custom hexadecimal color for your registration page. Format: FFFFFF without the pound.</param>
    /// <param name="boxBorderColor">Custom hexadecimal color for your registration page. Format: FFFFFF without the pound.</param>
    /// <param name="boxHeaderBackgroundColor">Custom hexadecimal color for your registration page. Format: FFFFFF without the pound.</param>
    /// <param name="boxHeaderTextColor">Custom hexadecimal color for your registration page. Format: FFFFFF without the pound.</param>        
    public static Process CreateNewEvent(
                                    string title,
                                    string startDate,
                                    string endDate,
                                    string timezone,
                                    int organizerId,
                                    int venueId,
                                    string description = "",
                                    int privacy = 1,
                                    string personalizedUrl = "",
                                    int capacity = 0,
                                    string currency = "",
                                    string status = "",
                                    string customHeader = "",
                                    string customFooter = "",
                                    string backgroundColor = "",
                                    string textColor = "",
                                    string linkColor = "",
                                    string titleTextColor = "",
                                    string boxBackgroundColor = "",
                                    string boxTextColor = "",
                                    string boxBorderColor = "",
                                    string boxHeaderBackgroundColor = "",
                                    string boxHeaderTextColor = "")
    {
        NameValueCollection queryStringValues = new NameValueCollection();
        queryStringValues.Add("title", title.ToString());
        queryStringValues.Add("start_date", startDate);
        queryStringValues.Add("end_date", endDate);
        queryStringValues.Add("timezone", timezone);
        queryStringValues.Add("privacy", privacy.ToString());
        queryStringValues.Add("personalized_url", personalizedUrl);
        queryStringValues.Add("venue_id", venueId.ToString());
        queryStringValues.Add("organizer_id", organizerId.ToString());
        queryStringValues.Add("capacity", capacity.ToString());
        queryStringValues.Add("currency", currency);
        queryStringValues.Add("status", status);
        queryStringValues.Add("custom_header", customHeader);
        queryStringValues.Add("custom_footer", customFooter);
        queryStringValues.Add("background_color", backgroundColor);
        queryStringValues.Add("text_color", textColor);
        queryStringValues.Add("link_color", linkColor);
        queryStringValues.Add("title_text_color", titleTextColor);
        queryStringValues.Add("box_background_color", boxBackgroundColor);
        queryStringValues.Add("box_text_color", boxTextColor);
        queryStringValues.Add("box_border_color", boxBorderColor);
        queryStringValues.Add("box_header_background_color", boxHeaderBackgroundColor);
        queryStringValues.Add("box_header_text_color", boxHeaderTextColor);

        var createEventUrl = BuildRequestUrl(UserCreateEventUrl, "", queryStringValues);
        var receiveStream = new WebClient().OpenRead(createEventUrl);
        var response = new StreamReader(receiveStream).ReadToEnd();

        XmlSerializer serializer = new XmlSerializer(typeof(Process));
        if (serializer.CanDeserialize(new XmlTextReader(new StringReader(response))))
        {
            return serializer.Deserialize(new StringReader(response)) as Process;
        }
        else
        {
            DeserializeError(response);
        }

        return null;
    }

    /// <summary>
    /// This method retrieves the data for a given user. If no userId or email provided, returns the information of the current API user (owner of ApiKey).
    /// </summary>
    /// <param name="userId">The ID of the subuser account.</param>
    /// <param name="email">The email address of the subuser account.</param>
    public static User GetUserInfo(string userId = "", string email = "")
    {
        NameValueCollection queryStringValues = new NameValueCollection();
        queryStringValues.Add("user_id", userId);
        queryStringValues.Add("email", email);

        var url = BuildRequestUrl(UserInfoUrl, "", queryStringValues);

        var receiveStream = new WebClient().OpenRead(url);
        var response = new StreamReader(receiveStream).ReadToEnd();

        XmlSerializer serializer = new XmlSerializer(typeof(User));
        if (serializer.CanDeserialize(new XmlTextReader(new StringReader(response))))
        {
            return serializer.Deserialize(new StringReader(response)) as User;
        }
        else
        {
            DeserializeError(response);
        }

        return null;
    }

    /// <summary>
    /// Returns the data for a given event.
    /// </summary>
    /// <param name="eventId">The ID of the event.</param>        
    public static Event GetEvent(int eventId)
    {
        NameValueCollection queryStringValues = new NameValueCollection();
        queryStringValues.Add("id", eventId.ToString());

        var url = BuildRequestUrl(GetEventUrl, "", queryStringValues);

        var receiveStream = new WebClient().OpenRead(url);
        var response = new StreamReader(receiveStream).ReadToEnd();

        XmlSerializer serializer = new XmlSerializer(typeof(Event));
        if (serializer.CanDeserialize(new XmlTextReader(new StringReader(response))))
        {
            return serializer.Deserialize(new StringReader(response)) as Event;
        }
        else
        {
            DeserializeError(response);
        }

        return null;
    }

    /// <summary>
    /// This method renders a widget with a list of user's events.
    /// </summary>
    /// <param name="user">The user email. Defaults to the authenticated user if not provided.</param>
    /// <param name="count">The number of events to show. Defaults to 10.</param>
    /// <param name="title">The Widget title.</param>
    public static HelperResult GetUserEventsWidget(string user = "", int count = 10, string title = "")
    {
        if (title.IsEmpty())
        {
            var userInfo = GetUserInfo(user);
            title = string.Format("Events of {0}", userInfo.FirstName ?? userInfo.Email);
        }

        var eventList = GetUserEventList(user);

        return EventbriteApi.Widgets.UserEvents.GetHtml(eventList.Take(count), title);
    }

    /// <summary>
    /// This method shows a widget that allows to you sell tickets to an event from your site.
    /// </summary>
    /// <param name="eventId">The ID of the event.</param> 
    /// <param name="height">The Height of the event.</param> 
    public static HelperResult GetTicketWidget(int eventId, int height = 0)
    {
        Event eventbriteEvent = GetEvent(eventId);
        if (height == 0)
        {
            // Calculate the iframe height (pixels) based on the amount of available tickets
            height = 132 + (34 * eventbriteEvent.VisibleTickets);
        }

        return EventbriteApi.Widgets.Ticket.GetHtml(eventId, height, eventbriteEvent.Title);
    }

    /// <summary>
    /// This method shows a widget that allows to you display a custom calendar widget for your upcoming events.
    /// </summary>
    /// <param name="eventId">The ID of the event.</param>
    /// <param name="width">The with of the widget in pixels.</param>
    /// <param name="height">The height of the widget in pixels.</param>
    public static HelperResult GetCalendarWidget(int eventId, int width = 220, int height = 550)
    {
        Event eventbriteEvent = GetEvent(eventId);

        return EventbriteApi.Widgets.Calendar.GetHtml(eventId, eventbriteEvent.Title, width, height);
    }

    private static string BuildRequestUrl(string requestBaseUrl, string user, NameValueCollection queryStringParameters)
    {
        string requestCompleteUrl;
        if (!user.IsEmpty())
        {
            requestCompleteUrl = string.Format("{0}?app_key={1}&user={2}", requestBaseUrl, AppKey, user);
        }
        else if (!UserKey.IsEmpty())
        {
            requestCompleteUrl = string.Format("{0}?app_key={1}&user_key={2}", requestBaseUrl, AppKey, UserKey);
        }
        else if (!User.IsEmpty())
        {
            requestCompleteUrl = string.Format("{0}?app_key={1}&user={2}&password={3}", requestBaseUrl, AppKey, User, Password);
        }
        else
        {
            throw new ArgumentException("You should specify either the User Key property, the User/Password properties, or the user parameter.");
        }

        string queryString = BuildQueryString(queryStringParameters);

        if (!queryString.IsEmpty())
        {
            requestCompleteUrl = string.Format("{0}&{1}", requestCompleteUrl, queryString);
        }

        return requestCompleteUrl;
    }

    private static string BuildQueryString(NameValueCollection parameters)
    {
        NameValueCollection queryString = HttpUtility.ParseQueryString("");

        foreach (var key in parameters.AllKeys)
        {
            if (!parameters[key].IsEmpty())
            {
                queryString.Add(key, parameters[key]);
            }
        }

        return queryString.ToString();
    }

    private static void DeserializeError(String response)
    {
        var error = new XmlSerializer(typeof(Error)).Deserialize(new StringReader(response)) as Error;

        throw new EventbriteException(error.ErrorType, error.ErrorMessage);
    }
}

